﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.NetworkAnalyst;
using ESRI.ArcGIS.Geometry;
using Entity;

namespace Utilities
{
    public class CampusNALayer
    {
        private const String FGDB_WORKSPACE = @"E:\VirtualCampus\ArcGlobeData\ZhiMingBuilding\Campus3D\BuildingWays.gdb";
        //private const String FGDB_WORKSPACE = @"\..\..\..\..\..\Data\SanFrancisco\SanFrancisco.gdb";
        private const String INPUT_FACILITY_FC = "TFireStation";
        private const String INPUT_INCIDENCE_FC = "TFirePump";
        private const String INPUT_HOSPITAL_FC = "THospital";
        private const String INPUT_SAFTY_FC = "TSalftyPois";
        private const String INPUT_NAME_FIELD = "Name";
        private const String FEATURE_DATASET = "SchoolRoad";
        private const String NETWORK_DATASET = "SchoolRoad_ND";

        private IFeatureClass _RouteFC;
        private int _ClosestPumpId = 0;
        private EFacility _EClosestPump;
        public EFacility ClosestPump
        {
            get { return this._EClosestPump;}            
        }
        /// <summary>
        /// Create the analysis layer, load the locations, solve the analysis, and write to disk
        /// </summary>
        public Collection<ERouteNode> SolveRoute(int fireSource)
        {
            // Open the feature workspace, input feature class, and network dataset
            // As Workspace Factories are Singleton objects, they must be instantiated with the Activator
            IWorkspaceFactory workspaceFactory = System.Activator.CreateInstance(System.Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory")) as IWorkspaceFactory;
            IFeatureWorkspace featureWorkspace = workspaceFactory.OpenFromFile(FGDB_WORKSPACE, 0) as IFeatureWorkspace;
            IFeatureClass inputIncidentsFClass = featureWorkspace.OpenFeatureClass(INPUT_INCIDENCE_FC);
            IFeatureClass inputFacilityFClass = featureWorkspace.OpenFeatureClass(INPUT_FACILITY_FC);            

            // Obtain the dataset container from the workspace
            ESRI.ArcGIS.Geodatabase.IFeatureDataset featureDataset = featureWorkspace.OpenFeatureDataset(FEATURE_DATASET);
            var featureDatasetExtensionContainer = featureDataset as ESRI.ArcGIS.Geodatabase.IFeatureDatasetExtensionContainer;
            ESRI.ArcGIS.Geodatabase.IFeatureDatasetExtension featureDatasetExtension = featureDatasetExtensionContainer.FindExtension(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset);
            var datasetContainer3 = featureDatasetExtension as ESRI.ArcGIS.Geodatabase.IDatasetContainer3;

            // Use the container to open the network dataset.
            ESRI.ArcGIS.Geodatabase.IDataset dataset = datasetContainer3.get_DatasetByName(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset, NETWORK_DATASET);
            INetworkDataset networkDataset = dataset as INetworkDataset;

            // Create the Closest Facility NALayer
            INALayer naLayer = CreateClosestFacilitLayer("CampusRoute", networkDataset);
            INAContext naContext = naLayer.Context;
            
            INAClass facilityNAClass = naContext.NAClasses.get_ItemByName("Facilities") as INAClass;
            INAClass incidentsNAClass = naContext.NAClasses.get_ItemByName("Incidents") as INAClass;                  
            IFeatureClass routesFC = naContext.NAClasses.get_ItemByName("CFRoutes") as IFeatureClass;
            _RouteFC = routesFC;

            // Load the Stops
            GetClosestPump(fireSource);
            INAClassFieldMap naClassFieldMap = new NAClassFieldMapClass();
            naClassFieldMap.set_MappedField("Name", INPUT_NAME_FIELD);

            INAClassLoader naFacilityLoader = new NAClassLoaderClass();
            naFacilityLoader.Locator = naContext.Locator;
            naFacilityLoader.Locator.SnapTolerance = 0.5;
            naFacilityLoader.Locator.SnapToleranceUnits = esriUnits.esriMeters;
            naFacilityLoader.NAClass = facilityNAClass;
            naFacilityLoader.FieldMap = naClassFieldMap;

            INAClassLoader naIncidentsLoader = new NAClassLoaderClass();
            naIncidentsLoader.Locator = naContext.Locator;
            naIncidentsLoader.Locator.SnapTolerance = 0.5;
            naIncidentsLoader.Locator.SnapToleranceUnits = esriUnits.esriMeters;
            naIncidentsLoader.NAClass = incidentsNAClass;
            naIncidentsLoader.FieldMap = naClassFieldMap;

            
            // Avoid loading network locations onto non-traversable portions of elements
            //INALocator3 locator = naContext.Locator as INALocator3;
            //locator.ExcludeRestrictedElements = true;
            //locator.CacheRestrictedElements(naContext);

            int rowsInCursor = 0;
            int rowsLocated = 0;
            naFacilityLoader.Load(inputFacilityFClass.Search(new QueryFilterClass(), false) as ICursor, new CancelTrackerClass(), ref rowsInCursor, ref rowsLocated);

            IQueryFilter incidentsFilter = new QueryFilterClass();
            incidentsFilter.WhereClause = "Id="+_ClosestPumpId;
            
            int rowsIncidentsCursor = 0;
            int rowsIncidentsLocated = 0;
            naIncidentsLoader.Load(inputIncidentsFClass.Search(incidentsFilter, false) as ICursor, new CancelTrackerClass(), ref rowsIncidentsCursor, ref rowsIncidentsLocated);
                        
            //Message all of the network analysis agents that the analysis context has changed
            ((INAContextEdit)naContext).ContextChanged();

            //Solve
            INASolver naSolver = naContext.Solver;
            naSolver.Solve(naContext, new GPMessagesClass(), new CancelTrackerClass());
            
            //Save the layer to disk
            //SaveLayerToDisk(naLayer as ILayer, @"D:\data\CampusRoute.lyr");
            return GetRouteNodes(routesFC);
        }

        public Collection<ERouteNode> SolveHospitalRoute()
        {
            // Open the feature workspace, input feature class, and network dataset
            // As Workspace Factories are Singleton objects, they must be instantiated with the Activator
            IWorkspaceFactory workspaceFactory = System.Activator.CreateInstance(System.Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory")) as IWorkspaceFactory;
            IFeatureWorkspace featureWorkspace = workspaceFactory.OpenFromFile(FGDB_WORKSPACE, 0) as IFeatureWorkspace;
            IFeatureClass inputIncidentsFClass = featureWorkspace.OpenFeatureClass(INPUT_HOSPITAL_FC);
            IFeatureClass inputFacilityFClass = featureWorkspace.OpenFeatureClass(INPUT_SAFTY_FC);

            // Obtain the dataset container from the workspace
            ESRI.ArcGIS.Geodatabase.IFeatureDataset featureDataset = featureWorkspace.OpenFeatureDataset(FEATURE_DATASET);
            var featureDatasetExtensionContainer = featureDataset as ESRI.ArcGIS.Geodatabase.IFeatureDatasetExtensionContainer;
            ESRI.ArcGIS.Geodatabase.IFeatureDatasetExtension featureDatasetExtension = featureDatasetExtensionContainer.FindExtension(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset);
            var datasetContainer3 = featureDatasetExtension as ESRI.ArcGIS.Geodatabase.IDatasetContainer3;

            // Use the container to open the network dataset.
            ESRI.ArcGIS.Geodatabase.IDataset dataset = datasetContainer3.get_DatasetByName(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset, NETWORK_DATASET);
            INetworkDataset networkDataset = dataset as INetworkDataset;

            // Create the Closest Facility NALayer
            INALayer naLayer = CreateClosestFacilitLayer("CampusRoute", networkDataset);
            INAContext naContext = naLayer.Context;

            INAClass facilityNAClass = naContext.NAClasses.get_ItemByName("Facilities") as INAClass;
            INAClass incidentsNAClass = naContext.NAClasses.get_ItemByName("Incidents") as INAClass;
            IFeatureClass routesFC = naContext.NAClasses.get_ItemByName("CFRoutes") as IFeatureClass;
            _RouteFC = routesFC;

            // Load the Stops           
            INAClassFieldMap naClassFieldMap = new NAClassFieldMapClass();
            naClassFieldMap.set_MappedField("Name", INPUT_NAME_FIELD);

            INAClassLoader naFacilityLoader = new NAClassLoaderClass();
            naFacilityLoader.Locator = naContext.Locator;
            naFacilityLoader.Locator.SnapTolerance = 0.5;
            naFacilityLoader.Locator.SnapToleranceUnits = esriUnits.esriMeters;
            naFacilityLoader.NAClass = facilityNAClass;
            naFacilityLoader.FieldMap = naClassFieldMap;

            INAClassLoader naIncidentsLoader = new NAClassLoaderClass();
            naIncidentsLoader.Locator = naContext.Locator;
            naIncidentsLoader.Locator.SnapTolerance = 0.5;
            naIncidentsLoader.Locator.SnapToleranceUnits = esriUnits.esriMeters;
            naIncidentsLoader.NAClass = incidentsNAClass;
            naIncidentsLoader.FieldMap = naClassFieldMap;

                        
            int rowsInCursor = 0;
            int rowsLocated = 0;
            naFacilityLoader.Load(inputFacilityFClass.Search(new QueryFilterClass(), false) as ICursor, new CancelTrackerClass(), ref rowsInCursor, ref rowsLocated);

            //IQueryFilter incidentsFilter = new QueryFilterClass();
            //incidentsFilter.WhereClause = "Id=" + _ClosestPumpId;

            int rowsIncidentsCursor = 0;
            int rowsIncidentsLocated = 0;
            naIncidentsLoader.Load(inputIncidentsFClass.Search(new QueryFilterClass(), false) as ICursor, new CancelTrackerClass(), ref rowsIncidentsCursor, ref rowsIncidentsLocated);

            //Message all of the network analysis agents that the analysis context has changed
            ((INAContextEdit)naContext).ContextChanged();

            //Solve
            INASolver naSolver = naContext.Solver;
            naSolver.Solve(naContext, new GPMessagesClass(), new CancelTrackerClass());

            //Save the layer to disk
            //SaveLayerToDisk(naLayer as ILayer, @"D:\data\CampusRoute.lyr");
            return GetRouteNodes(routesFC);
        }
        private Collection<ERouteNode> GetRouteNodes(IFeatureClass routeFC)
        {
            IFeatureCursor pFeatureCursor;
            IFeature lineFeature;
            IPointCollection pointCollection;
            Collection<ERouteNode> nodes = new Collection<ERouteNode>();
            string lons = string.Empty;
            string lats = string.Empty;
            string alts = string.Empty;

            pFeatureCursor = routeFC.Search(null, false);
            lineFeature = pFeatureCursor.NextFeature();
            pointCollection = lineFeature.Shape as IPointCollection;

            for (int i = 0; i < pointCollection.PointCount; i++)
            {
                IPoint point = new PointClass();
                IZAware zAware = point as IZAware;
                zAware.ZAware = true;

                pointCollection.QueryPoint(i, point);
                IPoint poiPrj = EsriUtility.PCS2GCS(point.X, point.Y);
                
                ERouteNode node = new ERouteNode(poiPrj.X, poiPrj.Y, point.Z);
                lons = lons + poiPrj.X + ",";
                lats = lats + poiPrj.Y + ",";
                alts = alts + point.Z + ",";
                nodes.Add(node);
            }
            return nodes;
            
        }

        /// <summary>
        /// Create a new network analysis layer and set some solver settings
        /// </summary>
        private INALayer CreateClosestFacilitLayer(String layerName, INetworkDataset networkDataset)
        {
            //INARouteSolver naRouteSolver = new NARouteSolverClass();
            INAClosestFacilitySolver naClosestFacilitySolver = new NAClosestFacilitySolverClass();
            INASolverSettings naSolverSettings = naClosestFacilitySolver as INASolverSettings;
            naSolverSettings.ImpedanceAttributeName = "Length";
            INASolver naSolver = naClosestFacilitySolver as INASolver;

            //Get the NetworkDataset's Data Element
            IDatasetComponent datasetComponent = networkDataset as IDatasetComponent;
            IDENetworkDataset deNetworkDataset = datasetComponent.DataElement as IDENetworkDataset;

            //Create the NAContext and bind to it
            INAContext naContext;
            naContext = naSolver.CreateContext(deNetworkDataset, layerName);
            INAContextEdit naContextEdit = naContext as INAContextEdit;
            naContextEdit.Bind(networkDataset, new GPMessagesClass());

            //Create the NALayer
            INALayer naLayer;
            naLayer = naSolver.CreateLayer(naContext);
            (naLayer as ILayer).Name = layerName;

            //Set properties on the route solver interface
            naClosestFacilitySolver.DefaultTargetFacilityCount = 1;
            naClosestFacilitySolver.OutputLines = esriNAOutputLineType.esriNAOutputLineTrueShapeWithMeasure;

            // Update the context based on the changes made to the solver settings
            naSolver.UpdateContext(naContext, deNetworkDataset, new GPMessagesClass());

            //Return the layer
            return naLayer;
        }

        /// <summary>
        /// Write the NALayer out to disk as a layer file.
        /// </summary>
        private void SaveLayerToDisk(ILayer layer, String path)
        {
            try
            {
                Console.WriteLine("Writing layer file containing analysis to " + path);
                ILayerFile layerFile = new LayerFileClass();
                layerFile.New(path);
                layerFile.ReplaceContents(layer as ILayer);
                layerFile.Save();
                Console.WriteLine("Writing layer file successfully saved");
            }
            catch (Exception err)
            {
                // Write out errors
                Console.WriteLine(err.Message);
            }
        }
        public EFacility GetClosestPump(int fireSource)
        {            
            double minLen = 10000;            
            EFacility pump=new EFacility();
            PropertyQuery pq = new PropertyQuery();
            IPoint point1 = pq.QueryPoint(fireSource, "TClassRoom", "CRCode");
            IPoint point2=new PointClass(); 
            for (int i = 0; i < 3; i++)
            {
                point2 = pq.QueryPoint(i+1, "TFirePump", "Id");
                double distance=EsriUtility.GetDistanceBetween(point1, point2);                 
                if(distance<minLen)
                {
                    pump.LenToIncidence = distance;
                    IPoint gcsPoint = EsriUtility.PCS2GCS(point2.X, point2.Y);
                    pump.Lon = gcsPoint.X;
                    pump.Lat = gcsPoint.Y;
                    int fno = i + 1;
                    pump.FacilityNo = fno.ToString();
                    this._ClosestPumpId = i + 1;
                }
            }
            this._EClosestPump = pump;
            return pump;
        }
    }
}
